package com.xquant.trade.trade.service;


import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xquant.common.core.constant.ErrMsgConstant;
import com.xquant.common.core.constant.EventStatus;
import com.xquant.common.core.exception.ServiceException;
import com.xquant.common.core.utils.ParseUtils;
import com.xquant.common.engine.xPP.cxf.webservice.*;
import com.xquant.common.engine.xPP.request.CalcEngineParam;
import com.xquant.common.engine.xPP.request.CalcPricingParam;
import com.xquant.common.engine.xPP.request.Instrument;
import com.xquant.trade.utils.ResponseVo;
import com.xquant.trade.risk.service.RiskService;
import com.xquant.trade.trade.entity.Trade;
import com.xquant.trade.trade.entity.Tremind;
import com.xquant.trade.trade.mapper.TequityStructureMapper;
import com.xquant.trade.trade.mapper.TradeMapper;
import com.xquant.trade.trade.mapper.TremindMapper;
import com.xquant.trade.utils.DateUtils;
import org.apache.commons.collections.MapUtils;
import org.quartz.JobExecutionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 存续期调用 存续 到期 敲入 敲出
 */
@Component
public class DurationCalcService {
    private static final Logger logger = LoggerFactory.getLogger(DurationCalcService.class);
    @Autowired
    private TradeMapper tradeMapper;
    @Autowired
    private SettleService settleService;
    @Autowired
    private RiskService riskService;
    @Autowired
    private TequityStructureMapper tequityStructureMapper;
    @Autowired
    private TremindMapper tremindMapper;


    /**
     * 存续期事件计算 任务调度
     */
    public void calTask(JobExecutionContext jobExecutionContext) throws ParseException {
        Map param = new HashMap();
        param.put("userId", "admin");

        String begDate = MapUtils.getString(jobExecutionContext.getMergedJobDataMap(), "begDate");
        String endDate = MapUtils.getString(jobExecutionContext.getMergedJobDataMap(), "endDate");
        if (begDate != null && endDate != null) {
            List<String> days = DateUtils.getRangeDate(begDate, endDate);
            days.stream().forEach(data -> {
                try {
                    calcDuration(param, data);
                } catch (ParseException e) {
//                    logger.error("日期转换异常");
                    throw new ServiceException(ErrMsgConstant.DURATION_TASK_ERR,e.getMessage());
                }
                execExpireContract(param, data);
            });
        } else {
            //存续判断
            calcDuration(param, null);
            //到期判断
            execExpireContract(param, null);
        }
    }

    public void calTaskRemind(JobExecutionContext jobExecutionContext) throws ParseException {
        Map param = new HashMap();
        String begDate = MapUtils.getString(jobExecutionContext.getMergedJobDataMap(), "begDate");
        String endDate = MapUtils.getString(jobExecutionContext.getMergedJobDataMap(), "endDate");
        if (begDate != null && endDate != null) {
            List<String> days = DateUtils.getRangeDate(begDate, endDate);
            days.stream().forEach(data -> {
                param.put("remindDate", data);
                insertTReminds(param);
            });
        } else {
            param.put("remindDate", DateUtils.getCurr_YYYY_MM_DD());
            insertTReminds(param);
        }
    }

    /**
     * 交易提醒 插入remind表中没有的交易 任务调度
     */
    public void insertTReminds(Map param) {
        List<Map> mapList = tremindMapper.queryTradeListNotInRemind(param);
        List<Tremind> tremindList = new ArrayList<>();
        mapList.forEach(item -> {
            Tremind tremind = new Tremind();
            tremind.setiCode((String) item.get("I_CODE"));
            tremind.setTradeId(String.valueOf(item.get("TRADE_ID")));
            tremind.setRemindType((String) item.get("EVENT_STATUS"));
            tremind.setMaturityDate((String) item.get("MATURITY_DATE"));
            tremind.setRemindDate((String) item.get("MATURITY_DATE"));
            tremindList.add(tremind);
        });
        if (tremindList.size() != 0) {
            tremindMapper.insertOrUpdateTreminds(tremindList);
        }
    }

    /**
     * 设置交易的到期状态
     *
     * @param map      中包含blcDate
     * @param dealDate 执行日期 如果不为Null 则按dealDate执行
     */
    public void execExpireContract(Map map, String dealDate) {
        if (dealDate != null) {
            map.put("blcDate", dealDate);
        } else {
            map.put("blcDate", DateUtils.getCurr_YYYY_MM_DD());
        }
        calcExpireContractByDay(map);
    }

    public void calcExpireContractByDay(Map map) {
        //处理到期日小于等于今天 并且存续期或状态为空的
        List<Map> lst = tradeMapper.queryExpireTradeList(map);
        if (lst.size() > 0) {
            for (int i = 0; i < lst.size(); i++) {
                Map param = new HashMap();
                param.put("tradeId", lst.get(i).get("trade_Id"));
                param.put("iCode", lst.get(i).get("I_CODE"));
                param.put("aType", lst.get(i).get("A_TYPE"));
                param.put("mType", lst.get(i).get("M_TYPE"));
                param.put("eventStatus", "expire");
                param.put("calcDate", map.get("blcDate"));
                tequityStructureMapper.updateTequityStructure(param);

                tequityStructureMapper.updateTequityDurationStatus(param);

                Tremind tremind = new Tremind();
                tremind.setiCode((String) lst.get(i).get("I_CODE"));
                tremind.setTradeId(String.valueOf(lst.get(i).get("trade_Id")));
                tremind.setMaturityDate((String) lst.get(i).get("MATURITY_DATE"));
                tremind.setRemindDate((String) lst.get(i).get("MATURITY_DATE"));
                tremind.setRemindType("expire");
                tremindMapper.insertOrUpdateTremind(tremind);
            }
        }
        tequityStructureMapper.updateDurationExpire(map);
    }

    /**
     * 存续期处理
     *
     * @param param
     * @param dealDate 执行的日期  如果为null则取系统的最新日期
     */
    public void calcDuration(Map param, String dealDate) throws ParseException {
        String blcDate = riskService.getBlcDate();
        if (StrUtil.isNullOrUndefined(dealDate)) {
            param.put("calendarDate", blcDate);
        } else {
            param.put("calendarDate", dealDate);
            blcDate = dealDate;
        }
        riskService.getRealBlcDate(param);

        param.put("blcDate", blcDate);
        // 查询计算列表  blcDate在期权开始日期和到期日范围内的
        List<Trade> tradeList = tradeMapper.queryTrade4OtcOptionDuration(param);
        List<ResponseVo> responseVosList = new ArrayList<>();
        for (int i = 0; i < tradeList.size(); i++) {
            Trade trade = tradeList.get(i);

            // 设置uCode
            String pCalc = MapUtils.getString(trade.getInstrumentInfo(), "pCalc");
            Instrument instrument = JSONObject.parseObject(pCalc, Instrument.class);
            // 计算
            CalcPricingParam calcPricingParam = new CalcPricingParam();
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            if (trade.getMaturityDate() != null) {
                if (format.parse(trade.getMaturityDate()).getTime() < format.parse(blcDate).getTime()) {
                    calcPricingParam.setValueDate(trade.getMaturityDate());
                }
            }else {
                calcPricingParam.setValueDate(blcDate);
            }
            //calcPricingParam.setCfgId("xir1");

            CalcEngineParam calcEngineParam = new CalcEngineParam();

            calcEngineParam.setInstrument(instrument);
            calcEngineParam.setCalcPricingParam(calcPricingParam);
            try {
//                calcEngineParam = calcCommonService4OtcOptionSplitService.beforeCalc(calcEngineParam);

                EventInfoParam eventInfoParam = new EventInfoParam();
                String iCode = trade.getiCode();
                String mType = trade.getmType();
                String aType = trade.getaType();
                String instrumentID = Base64.getEncoder().encodeToString(iCode.getBytes("GBK")) + ",OPT_S," + mType;
                logger.debug("instrumentID:" + instrumentID);
                eventInfoParam.setInstrumentID(instrumentID);
                eventInfoParam.setValueDate(blcDate);
                eventInfoParam.setStreamID("");

                PersonalInsParam insParam = new PersonalInsParam();
                String fpml = Base64.getEncoder().encodeToString(calcEngineParam.getInstrument().getTermsheet().toString().getBytes("GBK"));
                insParam.setFpML(fpml);
                insParam.setTermSheetType("1");
                eventInfoParam.setInsParam(insParam);

                //ResponseVo responseVo = calcEngineService.calcEventInfo(eventInfoParam);
                ResponseVo responseVo = ResponseVo.success();
                logger.debug("responseVo.getErrorCode():" + responseVo.getErrorCode());
                logger.debug("responseVo.getErrorMsg():" + responseVo.getErrorMsg());
                if (responseVo.getErrorCode() == 0) {
                    logger.debug("responseVo.getResult():" + JSONUtil.toJsonStr(responseVo.getResult()));
                    Object result = responseVo.getResult();
                    ObjectMapper mapper = new ObjectMapper();
                    EventInfoResult eventInfoResult = mapper.convertValue(result, EventInfoResult.class);
                    ArrayOfEventInfo events = eventInfoResult.getEvents();
                    List<EventInfo> eventInfo = events.getEventInfo();

                    EventInfo info = eventInfo.get(0);
                    EventValueEnum eventValue = info.getEventValue();
                    EventTypeEnum eventType = info.getEventType();
                    String eValue = eventValue.value();
                    String eType = eventType.value();
                    logger.debug("EventValueEnum:" + eValue);
                    logger.debug("EventTypeEnum:" + eType);

                    String status = "subsist";
                    switch (eType) {
                        case EventStatus.NONE:
                            status = "subsist";
                            break;
                        case EventStatus.EXERCISE:
                            status = "exercise";
                            break;
                        case EventStatus.UP_IN:
                            status = "knockIn";
                            break;
                        case EventStatus.UP_OUT:
                            status = "knockOut";
                            break;
                        case EventStatus.DOWN_IN:
                            status = "knockIn";
                            break;
                        case EventStatus.DOWN_OUT:
                            status = "knockOut";
                            break;
                        case EventStatus.CASH_INCOME:
                            status = "subsist";
                            break;
                    }
                    switch (eValue) {
                        case EventStatus.UNTOUCHUNEXERCISE:
                            status = "subsist";
                            break;
                        case EventStatus.TOUCHEXERCISE:
                            status = "exercise";
                            break;
                        case EventStatus.TOUCHUNEFFECTIVE:
                            status = "subsist";
                            break;
                        case EventStatus.TOUCHEFFECTIVE:
                            status = "touch";
                            break;
                    }
                    if ("SingleSharkFin".equals(trade.getProductType())){
                        // 返回触碰则将状态置为敲出
                        if (EventStatus.TOUCHEXERCISE.equals(eventInfo.get(0).getEventValue().value())) {
                            status = "knockOut";
                        } else if (EventStatus.UNTOUCHUNEXERCISE.equals(eventInfo.get(0).getEventValue().value())) {
                            if (EventStatus.TOUCHEXERCISE.equals(eventInfo.get(1).getEventValue().value())) {
                                status = "exercise";
                            } else {
                                status = "subsist";
                            }
                        }
                    }
                    if ("MultiDigital".equals(trade.getProductType())){
                        // 根据返回的定盘价进行判断拿高收，状态改成“行权-高”，拿中收，状态为行权不变，拿低收，状态为到期不变；
                        if (EventStatus.TOUCHEXERCISE.equals(eventInfo.get(0).getEventValue().value())) {
                            BigDecimal fixingValue = new BigDecimal((Double) eventInfo.get(0).getFixingValue()).setScale(2, BigDecimal.ROUND_HALF_UP);
                            JSONArray customLegs = (JSONArray) instrument.getTermsheet().get("customLegs");
                            JSONObject customLeg = (JSONObject) customLegs.get(0);
                            JSONArray priceRanges = (JSONArray) customLeg.get("priceRanges");
                            JSONObject mid = (JSONObject) priceRanges.get(1);
                            BigDecimal floorPrice = BigDecimal.ZERO;
                            BigDecimal capPrice = BigDecimal.ZERO;
                            BigDecimal floorPricePecentage = ParseUtils.converToBigDecimal(mid.get("floorPricePecentage"));
                            BigDecimal capPricePecentage = ParseUtils.converToBigDecimal(mid.get("capPricePecentage"));
                            com.alibaba.fastjson.JSONArray underlyers = (JSONArray) customLeg.get("underlyers");
                            com.alibaba.fastjson.JSONObject underlyer = (com.alibaba.fastjson.JSONObject) underlyers.get(0);
                            BigDecimal spotPrice = ParseUtils.converToBigDecimal(underlyer.get("spotPrice"));
                            if (spotPrice.compareTo(BigDecimal.ZERO) == 0) {
                                status = "subsist";
                            } else {
                                floorPrice = spotPrice.multiply(floorPricePecentage).setScale(2, BigDecimal.ROUND_HALF_UP);
                                capPrice = spotPrice.multiply(capPricePecentage).setScale(2, BigDecimal.ROUND_HALF_UP);
                                if ("Call".equals(customLeg.get("optionType"))) {
                                    if (fixingValue.compareTo(floorPrice) < 0 ) {
                                        status = "subsist";
                                    }else if (fixingValue.compareTo(floorPrice) >= 0  && fixingValue.compareTo(capPrice) < 0) {
                                        status = "exercise";
                                    }else if (fixingValue.compareTo(capPrice) >= 0 ) {
                                        status = "exerciseHigh";
                                    }
                                }else if ("Put".equals(customLeg.get("optionType"))) {
                                    if (fixingValue.compareTo(floorPrice) < 0 ) {
                                        status = "exerciseHigh";
                                    }else if (fixingValue.compareTo(floorPrice) >= 0 && fixingValue.compareTo(capPrice) < 0 ) {
                                        status = "exercise";
                                    }else if (fixingValue.compareTo(capPrice) >= 0 ) {
                                        status = "subsist";
                                    }
                                }
                            }
                        }
                    }
                    Tremind tremind = new Tremind();
                    tremind.setiCode(iCode);
                    tremind.setTradeId(trade.getTradeId());
                    tremind.setRemindType(status);
                    tremind.setMaturityDate(trade.getMaturityDate());
                    tremind.setRemindDate("subsist".equals(status) ? trade.getMaturityDate(): blcDate);
                    tremindMapper.insertOrUpdateTremind(tremind);

                    // 更新struct表的状态，因为如果struct中的状态可能是行权，如果跑过去时间的交易获得的状态是存续，这时就不应该改struct的状态
                    HashMap<String, String> hashMap = new HashMap<>();
                    hashMap.put("eventStatus", status);
                    hashMap.put("tradeId",trade.getTradeId());
                    hashMap.put("iCode", iCode);
                    hashMap.put("aType", aType);
                    hashMap.put("mType", mType);
                    //更新金融工具状态 只需修改存续状态和空状态的
                    tequityStructureMapper.updateTequityStructureEventStatus(hashMap);

                    //按照日期和teadeID更新duration
                    Map<String, Object> map = new HashMap<>();
                    map.put("tradeId", trade.getTradeId());
                    map.put("eventStatus", status);
                    map.put("calcDate", blcDate);
                    tequityStructureMapper.updateTequityDurationStatus(map);

                } else {
                    // 更新Structure状态
                    HashMap<String, String> hashMap = new HashMap<>();
                    hashMap.put("eventStatus", "subsist");
                    hashMap.put("tradeId",trade.getTradeId());
                    tequityStructureMapper.updateTequityStructure(hashMap);

                    //按照日期和tradeID更新duration
                    Map<String, Object> map = new HashMap<>();
                    map.put("tradeId", trade.getTradeId());
                    map.put("eventStatus", "subsist");
                    map.put("calcDate", blcDate);
                    tequityStructureMapper.updateTequityDurationStatus(map);

                    Tremind tremind = new Tremind();
                    tremind.setiCode(iCode);
                    tremind.setTradeId(trade.getTradeId());
                    tremind.setRemindType("subsist");
                    tremind.setMaturityDate(trade.getMaturityDate());
                    tremind.setRemindDate(trade.getMaturityDate());
                    tremindMapper.insertOrUpdateTremind(tremind);
                }
                logger.info("iCode:" + iCode + " aType:" + aType + " mType:" + mType + "存续期事件计算完成");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {

            }
        }
    }
}

